use crate::core::rtweekend::*;
use crate::core::vec3::*;
use crate::core::ray::*;
use crate::core::hittable::*;

pub trait Material {
    fn scatter(&self, r_in: &Ray, rec: &HitRecord) -> Option<(Color3,Ray)>;
}

pub struct Lambertian{
    pub albedo: Color3,
}
impl Lambertian{
    pub fn from(a: &Color3) -> Self{
        Self{albedo:*a}
    }
}
impl Material for Lambertian{
    // fn scatter(&self, r_in: &Ray, rec: &HitRecord, attenuation: &mut Color3, scattered: &mut Ray) -> bool{
    //     let scatter_direction = &rec.normal.unwrap() + &random_unit_vector();
    //     if scatter_direction.near_zero(){
    //         scatter_direction = rec.normal.unwrap();
    //     }
    //     scattered = Ray::from(&rec.p, &scatter_direction);
    //     attenuation = albedo;
    //     dot(&scattered.direction(), &rec.normal.unwrap()) > 0
    // }

    fn scatter(&self, r_in: &Ray, rec: &HitRecord) -> Option<(Color3,Ray)>{
        let mut scatter_direction = &rec.normal.unwrap() + &random_unit_vector();
        if scatter_direction.near_zero(){
            scatter_direction = rec.normal.unwrap();
        }

        let scattered = Ray::from(&rec.p, &scatter_direction);
        let attenuation = self.albedo;
        Some((attenuation, scattered))
    }
}

pub struct Metal{
    pub albedo: Color3,
    pub fuzz: f64,
}
impl Metal{
    pub fn from(a: &Color3, f: f64) -> Self{
        Self{ albedo: *a, fuzz: f}
    }
}
impl Material for Metal{
    fn scatter(&self, r_in: &Ray, rec: &HitRecord) -> Option<(Color3, Ray)>{
        let reflected = reflect(&unit_vector(&r_in.direction()), &rec.normal.unwrap());
        let scattered = Ray::from(&rec.p, &(&reflected + &(self.fuzz*&random_in_unit_sphere())));
        let attenuation = self.albedo;
        if dot(&scattered.direction(), &rec.normal.unwrap()) > 0_f64 {
            Some((attenuation, scattered))
        }else{
            None
        }
    }
}

pub struct Dielectric{
    pub ir: f64,
}
impl Dielectric{
    pub fn from(index_of_refraction: f64) -> Self{
        Self{ ir: index_of_refraction}
    }

    fn reflectance(&self, cosin:f64, ref_idx:f64) -> f64{
        let r0 = (1.0 - ref_idx)/(1.0 + ref_idx);
        let r0 = r0*r0;
        r0 + (1.0 - r0) * (1.0 - cosin).powf(5.0)
    }
}
impl Material for Dielectric{
    fn scatter(&self, r_in: &Ray, rec: &HitRecord) -> Option<(Color3, Ray)>{
        let attenuation = Color3::from(1_f64, 1_f64, 1_f64);
        // let refraction_ratio = if rec.front_face.is_some() {1_f64 / self.ir} else {self.ir};

        let refraction_ratio: f64;        
        if let Some(true) = rec.front_face{
            refraction_ratio = 1_f64 / self.ir;
        }else{
            refraction_ratio = self.ir;
        }
        
        let unit_direction = unit_vector(&r_in.direction());
        let cos_theta = f64::min(dot(&((-1_f64)*&unit_direction), &rec.normal.unwrap()), 1_f64);
        let sin_theta = f64::sqrt(1_f64 - cos_theta * cos_theta);
        let cannot_refract = refraction_ratio * sin_theta > 1_f64;
        let direction:Vec3New;
        if cannot_refract || self.reflectance(cos_theta, refraction_ratio) > random_f64() {
            direction = reflect(&unit_direction, &rec.normal.unwrap());
        }else{
            direction = refract(&unit_direction, &rec.normal.unwrap(), refraction_ratio);
        }

        // let refracted = refract(&unit_direction, &rec.normal.unwrap(), refraction_ratio);
        let scattered = Ray::from(&rec.p, &direction);
        Some((attenuation, scattered))
    }
}

